/* 
 * File:   Disk.h
 * Author: chris
 *
 * Created on January 25, 2009, 1:18 PM
 * Modified; Feb 3, 2012; CGP
 *      Changes to have use physical disk device. 
 *      Apparently Mac OS X can use lseek for long seeks. 
 *          (http://lists.apple.com/archives/unix-porting/2002/Jul/msg00099.html)
 */

#ifndef _DISK_H
#define	_DISK_H


/**
 * Provides access to fixed-length blocks stored on a simulated
 * a disk device using a file.
 *
 * @author chris
 */
class Disk {
public:
    /** The default size of disk blocks. Use this, for example, when calling the
     *  Disk constructor. Only use this default if we are simulating the file system in a 
     *  file. I.e., don't use this if we are using a specific disk device. Instead,
     *  use GetSizes to get the sizes.
     */
    static const int DEFAULT_BLOCK_SIZE = 512; // bytes

    /**
     * @return the number of blocks on the Disk, as established via the constructor.
     */
	unsigned long numberOfBlocks();

    /**
     * @return the block size for the Disk, as established via the constructor.
     */
    unsigned int blockSize();

    /** When this class is used as the basis for a Linux device-based file system,
     * this method can be used to determine the size parameters for the Disk constructor.
     * BEWARE that this is dangerous. The data on your disk device will be lost.
     * Plus, the Linux system may not tell you are about to destroy the data on the
     * disk. Don't use your root (/) file system as the device!!
     * After compiling and linking your file system test code, you need to use 
     * this code as Linux root for this to work. 
     * You should first use the fdisk -l command (as root: e.g., sudo fdisk -l)
     * at the command line to find the device name of your disk. (If you run fdisk and
     * you are not root, it may not give you any output, not even an error message).
     * This can be a partition of a hard disk or a flash drive.
     * For example, when I installed Linux, I created a spare partition, which is /dev/sda6
     * and when I connect my flash drive, that is named /dev/sdb1
     * You should probably unmount this device the first time before you format with your
     * own file system format. E.g., at the command line, umount /dev/sdb1
	 * Note that I have coded this method for Mac OS X, but have yet to test it with a 
	 * Mac OS X physical disk. Make sure you #define the MACOSX or LINUX macros at the top
	 * of this file for the system you are on.
     *
     * @param device should refer to the file name for disk device.
     */
    static void GetSizes(const char *device, unsigned long &numberOfBlocks, unsigned int &blockSize);

private:
    long Seek(unsigned long offset);

public:
    /** Open the Disk.
     * @param deviceName should refer to the file name for the simulated disk or physical device that
     * will be used to store the disk blocks. E.g., "disk1" for a file name, or the name
     * of a physical disk device such as /dev/sda6.
     *
     * @param physicalDevice should be set to true iff we are not doing a simulation. That is,
     * if you will pass a device such as /dev/sda6 or /dev/sdb1 as the deviceName, then 
     * this parameter should be given as "true. 
     *
     * Does not format or otherwise alter the simulated disk (i.e., file).
     *
     * The disk (or file) itself does not record the number of blocks on the disk (or file)
     * nor does it record the block size,
     * so this info needs to be provided each time a Disk is opened.
     *
     * Only if physicalDevice = false, 
     * will a (simulation) file be created if it does not exist. If you are using fewer blocks than
     * in a pre-existing file, you will want to delete the file first.
     *
     * @param numberOfBlocks use the value from the GetSizes method if you are using a physical device.
     * @param blockSize use the value from the GetSizes method if you are using a physical device.
     */
    Disk(const char *deviceName, unsigned long numberOfBlocks, unsigned int blockSize, 
        bool physicalDevice=false);
    
    /**
     * Change the debug state. If debug state is false, debug info is not printed.
     * The debug state defaults to true.
     */
    void Debug(bool state);

    /**
      * Given that a Disk is open, "low-level" format it.
      * You will loose all data on the device (or file),
      * be careful! This class does not keep track of the
      * number of blocks nor the block size of the disk
      * in block 0 of the disk (nor anywhere else on the disk).
      *
      * (Rationale: Particularly, for the block size, in order for us to
      * read a block from the disk (e.g., block 0), we need the block size. If the block
      * size is on disk, and we need that to read a block, how do we know the block size
      * to read block 0, which contains the block size?)
      *
      * Return true iff the simulated disk could be formatted.
      */
    bool Format(); 

    /** Close the disk.
     * Returns true if the file was closed successfully.
     */
    bool Close(); 

    /** Write out a block of data to the given block number on the disk. 
      * @param blockNumber needs to be within range for this disk
      * (block numbers start at 0, and end at numberOfBlocks - 1).
      * @param block needs to have length (number of bytes)
      * the same as the block size for this disk.
      * @return true iff block write succeeded.
      */
    bool WriteBlock(unsigned int blockNumber, unsigned char* block);

    /** Read a block of data from the given block number on the disk.
      * @param blockNumber needs to be within range for this disk
      * (block numbers start at 0, and end at numberOfBlocks - 1).
      * @param block needs to have length (in bytes)
      * the same as the block size for this disk.
      * This must be previously allocated by the caller.
      * @return true iff block read succeeded.
      * The block of data in the parameter (block) passed may or may not
      * have been modified if this call returns false.
      */
    bool ReadBlock(unsigned int blockNumber, unsigned char *block);

    /** This is just for the instructors internal testing. This determines
      * if the file used by the disk has at least the number of blocks needed to represent
      * blockNumber. I.e., it checks if it's a valid block number.
      * @return true iff the block number is valid.
      */
    bool CheckBlock(unsigned int blockNumber);

    // Menu driven testing
    static void Test();

private:
    int m_fd; // file descriptor
    const char *m_deviceName;
    unsigned long m_numberOfBlocks;
    unsigned int m_blockSize;

    // whether or not to print out debugging info
    bool m_debugInfo;
	
 };




#endif	/* _DISK_H */

